import { Pomelo } from "./Pomelo";

// 定义 RSA 对象类型（根据实际情况调整）
interface RsaParams {
	// 示例属性，根据实际 RSA 结构补充
	key?: string;
	exponent?: string;
	[key: string]: unknown; // 允许任意字符串键
}

// 定义 sys 对象类型
interface SysHandshake {
	type: string;
	version: string;
	rsa: RsaParams;
}

// 定义 user 对象类型（根据实际情况调整）
interface UserHandshake {
	// 示例属性，根据实际需求补充
	[key: string]: unknown; // 允许任意字符串键
}
interface HandshakeBuffer {
	sys: SysHandshake;
	user: UserHandshake;
}

interface BuilderOptions {
	user?: UserHandshake; //用户信息参数
	encrypt?: boolean; //是否加密
	handshakeCallback?: (user: UserHandshake) => void;
}
export class Pitaya {
	//websocket
	private static ws: WebSocket | null;
	//回调map
	private static callbacks = new Map<number, Function>();
	//路由map
	private static routeMap = new Map<string, Function>();
	//请求的id,每次请求自增1
	private static reqId: number = 0;
	//整个pitaya连接成功后的回调
	private static onConnectSuccess: Function | null = null;

	private static JS_WS_CLIENT_TYPE = "cocos-websocket"; //pitaya_cocos
	private static JS_WS_CLIENT_VERSION = "1.0.0";
	//重连相关
	private static useCrypto = false; //是否使用加密
	private static reconnect = false; //是否开启重连
	private static reconnectTimer: number; //重连定时器
	private static reconnectUrl: string = ""; //重连的url
	private static reconnectAttempts = 0; //重连次数
	private static reconnectionDelay = 5000; //重连间隔
	private static DEFAULT_MAX_RECONNECT_ATTEMPTS = 10; //最大重连次数
	//心跳相关
	private static heartbeatInterval = 0;
	private static heartbeatTimeout = 0;
	private static nextHeartbeatTimeout = 0;
	private static gapThreshold = 100; // heartbeat gap threashold
	private static heartbeatId: number | null = null;
	private static heartbeatTimeoutId: number | null = null;
	private static handshakeCallback: ((user: UserHandshake) => void) | null = null;
	//路由 压缩字典相关
	private static routeRequestIdMap = new Map<number, string>(); //Map from request id to route
	private static dict = {}; // route string to code
	private static abbrs: { [key: string]: string } = {}; // code to route string

	private static handshakeBuffer: HandshakeBuffer = {
		sys: {
			type: this.JS_WS_CLIENT_TYPE,
			version: this.JS_WS_CLIENT_VERSION,
			rsa: {},
		},
		user: {},
	};

	//step1:包装成await/async
	public static async Init(host: string, port: number, path?: string, options?: BuilderOptions) {
		return new Promise((resolve, reject) => {
			this.onConnectSuccess = () => {
				resolve(true);
			};
			let url = `ws://${host}:${port}`;
			if (path) {
				url += `/${path}`;
			}
			if (options && options.user) {
				this.handshakeBuffer.user = options.user;
			}
			if (options && options.encrypt) {
				this.useCrypto = true;
				// You need to import or implement an RSA library. Example using 'jsrsasign':
				// npm install jsrsasign
				// import { RSAKey } from "jsrsasign";
				//todo 后续再考虑实现 是引入第三方库,还是用什么方式
				// const RSAKey = require("jsrsasign").RSAKey;
				// const rsa = new RSAKey();
				// rsa.generate(1024, "10001");
				// let data = {
				// 	rsa_n: rsa.n.toString(16),
				// 	rsa_e: rsa.e,
				// };
				// this.handshakeBuffer.sys.rsa = data;
			} else {
				this.handshakeBuffer.sys.rsa = {};
			}
			if (options && options.handshakeCallback) {
				this.handshakeCallback = options.handshakeCallback;
			}
			console.log("Pitaya Init url=", url);
			this.connector(url);
		});
	}

	//step2:初始化
	private static connector(url: string) {
		this.reconnectUrl = url; //保存重连的url
		let maxReconnectAttempts = this.DEFAULT_MAX_RECONNECT_ATTEMPTS;
		const thisObj = this; //保存this指针
		const onopen = function (event: Event) {
			if (!!thisObj.reconnect) {
				console.log("reconnect success, url=", url);
			}
			thisObj.reset();
			thisObj.sendHandShake(); // 发送握手包
		};
		const onmessage = function (event: MessageEvent) {
			thisObj.onMessage(event);
			// new package arrived, update the heartbeat timeout
			if (thisObj.heartbeatTimeout) {
				thisObj.nextHeartbeatTimeout = Date.now() + thisObj.heartbeatTimeout;
			}
		};
		const onerror = function (event: Event) {
			console.error("socket error: ", event);
		};
		const onclose = function (event: CloseEvent) {
			console.log("socket close: ", event, "reason=", event.reason, "code=", event.code);
			if (thisObj.reconnectAttempts < maxReconnectAttempts) {
				thisObj.reconnect = true;
				thisObj.reconnectAttempts++;
				thisObj.reconnectTimer = setTimeout(function () {
					thisObj.connector(thisObj.reconnectUrl);
				}, thisObj.reconnectionDelay);
				thisObj.reconnectionDelay *= 2;
			}
		};

		this.ws = new WebSocket(url);
		this.ws.binaryType = "arraybuffer";
		this.ws.onclose = onclose;
		this.ws.onerror = onerror;
		this.ws.onmessage = onmessage;
		this.ws.onopen = onopen;
	}
	private static send(packet: Uint8Array) {
		this.ws?.send(packet.buffer);
	}
	private static reset() {
		this.reconnect = false;
		this.reconnectionDelay = 1000 * 5;
		this.reconnectAttempts = 0;
		clearTimeout(this.reconnectTimer);
	}

	//step3:发送handshake
	private static async sendHandShake() {
		let handshakeBuffer = this.handshakeBuffer;
		let msg = Pomelo.Protocol.StrEncode(JSON.stringify(handshakeBuffer));
		let pkg = Pomelo.Package.Encode(Pomelo.Package.TYPE_HANDSHAKE, msg);
		this.send(pkg);
	}

	//step4:收到消息,分发
	private static onMessage(ev: any) {
		let msgs = Pomelo.Package.Decode(ev.data);
		if (Array.isArray(msgs)) {
			for (let i = 0; i < msgs.length; i++) {
				let msg = msgs[i];
				this.dispatch(msg);
			}
		} else {
			this.dispatch(msgs);
		}
	}

	/**
	 * step5, 解析消息分发器
	 * @param msg 收到的服务器消息
	 * @returns
	 */
	private static dispatch(msg: { type: number; body?: Uint8Array }) {
		switch (msg.type) {
			case Pomelo.Package.TYPE_HANDSHAKE:
				{
					let data = JSON.parse(Pomelo.Protocol.StrDecode(msg.body as Uint8Array));
					console.log(" handshake msg.code", data.code);
					if (data.code === 501) {
						console.error("client version not fulfilled"); //客户端版本不匹配
						return;
					}
					if (data.code !== 200) {
						console.error("handshake failed, code=", data);
						return;
					}
					this.handshakeInit(data);

					//step6: handshake回应，不然服务器会断开
					let obj = Pomelo.Package.Encode(Pomelo.Package.TYPE_HANDSHAKE_ACK);
					this.send(obj);

					//step7: 连接成功回调，加到step1的Promise
					if (this.onConnectSuccess) {
						this.onConnectSuccess();
					}
				}
				break;
			case Pomelo.Package.TYPE_HEARTBEAT:
				console.log("heartbeat received");
				this.heartbeat(msg.body);
				break;
			case Pomelo.Package.TYPE_DATA:
				this.onData(msg.body as ArrayBuffer);
				break;
			case Pomelo.Package.TYPE_KICK:
				this.onKick(msg.body);
				break;
		}
	}
	private static handshakeInit(data: any | HandshakeBuffer) {
		console.log("handshakeInit data=", data);
		if (data.sys && data.sys.heartbeat) {
			this.heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval
			this.heartbeatTimeout = this.heartbeatInterval * 2; // max heartbeat timeout
		} else {
			this.heartbeatInterval = 0;
			this.heartbeatTimeout = 0;
		}

		this.initData(data);

		if (typeof this.handshakeCallback === "function") {
			this.handshakeCallback(data.user);
		}
	}
	private static initData(data: any) {
		if (!data || !data.sys) {
			return;
		}
		let dict = data.sys.dict;

		//Init compress dict
		if (dict) {
			this.dict = dict;
			this.abbrs = {};

			for (var route in dict) {
				this.abbrs[dict[route]] = route;
			}
		}
	}

	private static heartbeat(data?: ArrayBuffer) {
		//heartbeat包的时候,收到的data是undefined,也就是只有一个type
		if (!this.heartbeatInterval) {
			// no heartbeat
			return;
		}

		var obj = Pomelo.Package.Encode(Pomelo.Package.TYPE_HEARTBEAT);
		if (this.heartbeatTimeoutId) {
			clearTimeout(this.heartbeatTimeoutId);
			this.heartbeatTimeoutId = null;
		}

		if (this.heartbeatId) {
			// already in a heartbeat interval
			return;
		}
		this.heartbeatId = setTimeout(() => {
			this.heartbeatId = null;
			console.log("heartbeat send");
			this.send(obj);

			this.nextHeartbeatTimeout = Date.now() + this.heartbeatTimeout;
			this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb, this.heartbeatTimeout);
		}, this.heartbeatInterval);
	}
	private static heartbeatTimeoutCb() {
		let gap = this.nextHeartbeatTimeout - Date.now();
		if (gap > this.gapThreshold) {
			this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb, gap);
		} else {
			console.error("server heartbeat timeout");
			this.disconnect();
		}
	}

	private static onKick(data?: ArrayBuffer) {
		console.log("onKick");
		let msg = JSON.parse(Pomelo.Protocol.StrDecode(data as Uint8Array));
		console.log("onKick msg=", msg);
	}

	private static onData(data: ArrayBuffer) {
		let msg = Pomelo.Message.Decode(data);
		//解析消息,如果用了路由压缩,这里还需要处理和还原路由压缩协议,参考starx-wsclient.js=>在pitaya库的examples中
		switch (msg.type) {
			case Pomelo.Message.TYPE_RESPONSE:
				{
					console.log("receive response msg.id=", msg.id, "route=", msg.route, "msg.body=", msg.body);
					let cb = this.callbacks.get(msg.id);
					this.callbacks.delete(msg.id);
					if (cb) {
						cb(msg.body);
					}
				}
				break;
			case Pomelo.Message.TYPE_PUSH:
				{
					console.log("receive push msg.route=", msg.route, "msg.body=", msg.body);
					let cb = this.routeMap.get(msg.route as string);
					if (cb) {
						cb(msg.body);
					}
				}
				break;
		}
	}

	/**
	 * Call方法，即req-resp模式
	 * @param route 路由
	 * @param msg
	 * @returns
	 */
	public static async Call(route: string, data: any) {
		if (!route) {
			return;
		}
		return new Promise((resolve, reject) => {
			this.reqId++;
			let type = Pomelo.Message.TYPE_REQUEST;
			let msg = Pomelo.Message.Encode(this.reqId, type, 0, route, data);
			let packet = Pomelo.Package.Encode(Pomelo.Package.TYPE_DATA, msg);
			this.ws?.send(packet.buffer);
			this.callbacks.set(this.reqId, (data: any) => {
				resolve(data);
			});
		});
	}

	/**
	 * Notify方法，即notify服务器，不需要收到回复消息
	 * @param route 路由
	 * @param data 发送服务器的数据
	 * @returns
	 */
	public static Notify(route: string, data: any) {
		let type = Pomelo.Message.TYPE_NOTIFY;
		let msg = Pomelo.Message.Encode(this.reqId, type, 0, route, data);
		let packet = Pomelo.Package.Encode(Pomelo.Package.TYPE_DATA, msg);
		this.ws?.send(packet.buffer);
	}

	/**
	 * Push方法，监听来自服务器的Push消息
	 * @param route 路由
	 * @param func 监听消息
	 * @returns
	 */
	public static Push(route: string, func: Function) {
		if (this.routeMap.has(route)) {
			return;
		}
		this.routeMap.set(route, func);
	}
	public static On(route: string, func: Function) {
		this.Push(route, func);
	}

	public static disconnect() {
		if (this.ws) {
			// if (this.ws.disconnect) this.ws.disconnect();
			if (this.ws.close) this.ws.close();
			console.log("disconnect");
			this.ws = null;
		}

		if (this.heartbeatId) {
			clearTimeout(this.heartbeatId);
			this.heartbeatId = null;
		}
		if (this.heartbeatTimeoutId) {
			clearTimeout(this.heartbeatTimeoutId);
			this.heartbeatTimeoutId = null;
		}
	}
}
